package com.test.netty.demo.netty.handler;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.test.netty.demo.util.MyException;
import com.test.netty.demo.util.ParamsAnalyze;
import com.test.netty.demo.util.SpringApplicationContext;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.util.ReferenceCountUtil;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.util.StringUtils;

/**
 * 服务处理类
 *
 * @author yousj
 */
public class HttpServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            if (msg instanceof FullHttpRequest) handleHttpRequest(ctx, (FullHttpRequest) msg);
            else ctx.close();
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
        Map<String, Object> params = null;
        Map<String, Object> result = new HashMap<>();
        String logId = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + "-" + this.hashCode() + "-" + result.hashCode();
        try {
            if ("GET".equals(request.getMethod().name())) {
                params = new ParamsAnalyze().analyzeGetParams(new QueryStringDecoder(request.getUri()), request);
                params.put("logId", logId);
            } else if ("POST".equals(request.getMethod().name())) {
                params = new ParamsAnalyze().analyzePostParams(new HttpPostRequestDecoder(request), request);
                params.put("logId", logId);
            }
            if (params == null || params.size() <= 2) {
                ctx.close();
                return;
            }
            String serviceName = (String) params.get("service");
            String method = (String) params.get("method");
            if (StringUtils.isEmpty(serviceName) || !SpringApplicationContext.getApplicationContext().containsBean(serviceName)) {
                throw new MyException("request path【" + serviceName + "】 is not found.");
            }
            params.remove("code");
            params.remove("mes");
            Object obj = SpringApplicationContext.getBean(serviceName);
            result.put("data", obj.getClass().getDeclaredMethod(method, Map.class).invoke(obj, params));
            result.put("code", params.getOrDefault("code", 200));
            result.put("mes", params.getOrDefault("mes", "ok"));
        } catch (MyException e) {
            result.put("code", e.getCode());
            result.put("mes", e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            result.put("code", 9999);
            result.put("mes", "Request rejected,please contact your administrator[" + logId + "]");
        } finally {
            sendHttpResponse(ctx, request, ("2".equals(params.get("isOnlyData"))) ? String.valueOf(result.get("data")) : JSONObject.toJSONString(("1".equals(params.get("isOnlyData")) && result.containsKey("data")) ? result.get("data") : result, (ValueFilter) (obj, key, value) -> {
                if (value instanceof Long) return String.valueOf(value);
                else return value;
            }), "1".equals(params.get("isJsonp")), params.containsKey("jsonpFunc") ? (String) params.get("jsonpFunc") : "");
        }
    }

    /**
     * 向客户端response发送数据信息
     */
    private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest request, String jStr, boolean isJsonP, String jsonpFunc) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer((isJsonP ? jsonpFunc + "(" + jStr + ")" : jStr).getBytes(Charset.forName("utf-8"))));
        response.headers().set(CONTENT_TYPE, "application/" + (isJsonP ? "javascript" : "json") + "; charset=utf-8");
        response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
        if (HttpHeaders.isKeepAlive(request)) response.headers().set(CONNECTION, Values.KEEP_ALIVE);
        ctx.write(response);
        ctx.flush();

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) {
        ctx.close();
    }

    /**
     * 得到访问者IP
     */
    private String getClientVisitIP(ChannelHandlerContext ctx, HttpRequest request) {
        String clientIP = (request.headers().get("X-Forwarded-For"));
        if (clientIP == null)
            clientIP = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
        return clientIP;
    }

}